﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;

namespace ArgusPCLib.Mathematics.Reflection
{
	public delegate T OperandParseHandler<T>(string text, string format, IFormatProvider formatProvider) where T : IOperand;
	public delegate bool OperandTryParseHandler<T>(string text, string foramt, IFormatProvider formatProvider, out T value) where T : IOperand;
	public delegate int OperandPartialParseHandler<T>(string text, int startIndex, string format, IFormatProvider formatProvider, out T value) where T : IOperand;

	public class OperandReflector
	{
		#region Static
		public static class MethodNames
		{
			public const string ParseName = "Parse";
			public const string TryParseName = "TryParse";
			public const string PartialParseName = "PartialParse";
		}

		static MethodInfo GetHandlerCore(Type operandType, string methodName, Type returnType, params Type[] parameterTypes)
		{
			MethodInfo mi = operandType.GetRuntimeMethod(methodName, parameterTypes);
			if (mi == null || !mi.IsStatic || mi.ReturnType != returnType)
			{
				StringBuilder sb = new StringBuilder();
				foreach (Type paramType in parameterTypes)
				{
					sb.Append(paramType);
					sb.Append(", ");
				}
				sb.Remove(sb.Length - 2, 2);
				throw new NotImplementedException(string.Format(Resources.Exceptions.StaticMethodMissing,
					operandType, returnType, methodName, sb.ToString()));
			}
			return mi;
		}

		static MethodInfo GetParseMethodInfo(Type operandType)
		{
			return OperandReflector.GetHandlerCore(operandType, MethodNames.ParseName, operandType,
				typeof(string), typeof(string), typeof(IFormatProvider));
		}

		//public static OperandParseHandler GetParseHandler(Type operandType)
		//{
		//	MethodInfo mi = OperandReflector.GetParseMethodInfo(operandType);
		//	return (text, format, formatProvider) => { return (IOperand)mi.Invoke(null, new object[] { text, format, formatProvider }); };
		//}

		public static OperandParseHandler<T> GetParseHandler<T>() where T : IOperand
		{
			MethodInfo mi = OperandReflector.GetParseMethodInfo(typeof(T));
			return (OperandParseHandler<T>)mi.CreateDelegate(typeof(OperandParseHandler<T>));
		}

		static MethodInfo GetTryParseMethodInfo(Type operandType)
		{
			return OperandReflector.GetHandlerCore(operandType, MethodNames.TryParseName, typeof(bool),
				typeof(string), typeof(string), typeof(IFormatProvider), operandType.MakeByRefType());
		}

		//public static OperandTryParseHandler GetTryParseHandler(Type operandType)
		//{
		//	MethodInfo mi = OperandReflector.GetTryParseMethodInfo(operandType);
		//	Func<string,string,IFormatProvider,IOperand, bool> func =
		//		(text,format,formatProvider,value)=>{return(bool)mi.Invoke(null, new object[]{text, format, formatProvider, value});};
		//	return (OperandTryParseHandler)func;
		//}

		public static OperandTryParseHandler<T> GetTryParseHandler<T>() where T : IOperand
		{
			MethodInfo mi = OperandReflector.GetTryParseMethodInfo(typeof(T));
			return (OperandTryParseHandler<T>)mi.CreateDelegate(typeof(OperandTryParseHandler<T>));
		}

		static MethodInfo GetPartialParseMethodInfo(Type operandType)
		{
			return OperandReflector.GetHandlerCore(operandType, MethodNames.PartialParseName, typeof(int),
				typeof(string), typeof(string), typeof(IFormatProvider), operandType.MakeByRefType());
		}

		//public static OperandPartialParseHandler GetPartialParseHandler(Type operandType)
		//{
		//	MethodInfo mi = OperandReflector.GetHandlerCore(operandType, MethodNames.PartialParseName, typeof(int),
		//		typeof(string), typeof(string), typeof(IFormatProvider), operandType.MakeByRefType());
		//	return (OperandPartialParseHandler)mi.CreateDelegate(typeof(OperandPartialParseHandler));
		//}

		public static OperandPartialParseHandler<T> GetPartialParseHandler<T>() where T : IOperand
		{
			MethodInfo mi = OperandReflector.GetPartialParseMethodInfo(typeof(T));
			return (OperandPartialParseHandler<T>)mi.CreateDelegate(typeof(OperandPartialParseHandler<T>));
		}

		public static IOperand Parse(Type operandType, string text, string format, IFormatProvider formatProvider)
		{
			return (IOperand)OperandReflector.GetParseMethodInfo(operandType).Invoke(null,
				new object[] { text, format, formatProvider });
		}

		public static IOperand Parse(Type operandType, string text, string format)
		{
			return OperandReflector.Parse(operandType, text, format, null);
		}

		public static IOperand Parse(Type operandType, string text, IFormatProvider formatProvider)
		{
			return OperandReflector.Parse(operandType, text, null, formatProvider);
		}

		public static bool TryParse(Type operandType, string text, string format, IFormatProvider formatProvider, out IOperand value)
		{
			object[] args = new object[] { text, format, formatProvider, null };
			bool RetVal = (bool)OperandReflector.GetTryParseMethodInfo(operandType).Invoke(null, args);
			value = args[3] as IOperand;
			return RetVal;
		}

		public static bool TryParse(Type operandType, string text, string format, out IOperand value)
		{
			return OperandReflector.TryParse(operandType, text, format, null, out value);
		}

		public static bool TryParse(Type operandType, string text, IFormatProvider formatProvider, out IOperand value)
		{
			return OperandReflector.TryParse(operandType, text, null, formatProvider, out value);
		}

		public static int PartialParse(Type operandType, string text, int startIndex, string format, IFormatProvider formatProvider, out IOperand value)
		{
			object[] args = new object[] { text, format, formatProvider, null };
			int RetVal = (int)OperandReflector.GetPartialParseMethodInfo(operandType).Invoke(null, args);
			value = args[3] as IOperand;
			return RetVal;
		}

		public static int PartialParse(Type operandType, string text, int startIndex, string format, out IOperand value)
		{
			return OperandReflector.PartialParse(operandType, text, startIndex, format, null, out value);
		}

		public static int PartialParse(Type operandType, string text, int startIndex, IFormatProvider formatProvider, out IOperand value)
		{
			return OperandReflector.PartialParse(operandType, text, startIndex, null, formatProvider, out value);
		}
		#endregion

		#region Instance
		MethodInfo _parse;
		MethodInfo _tryParse;
		MethodInfo _partialParse;
		public Type OperandType { get; private set; }

		public OperandReflector(Type operandType)
		{
			if (operandType == null)
				throw new ArgumentNullException();
			if (!operandType.Is(typeof(IOperand), false, false))
				throw new ArgumentException();

			this.OperandType = operandType;
		}

		public IOperand Parse(string text, string format, IFormatProvider formatProvider)
		{
			if (this._parse == null)
				this._parse = OperandReflector.GetParseMethodInfo(this.OperandType);
			return (IOperand)this._parse.Invoke(null, new object[] { text, format, formatProvider });
		}

		public IOperand Parse(string text, string format) { return this.Parse(text, format, null); }
		public IOperand Parse(string text, IFormatProvider formatProvider) { return this.Parse(text, null, formatProvider); }

		public bool TryParse(string text, string format, IFormatProvider formatProvider, out IOperand value)
		{
			if (this._tryParse == null)
				this._tryParse = OperandReflector.GetTryParseMethodInfo(this.OperandType);
			object[] args = new object[] { text, format, formatProvider, null };
			bool RetVal = (bool)this._tryParse.Invoke(null, args);
			value = args[3] as IOperand;
			return RetVal;
		}

		public bool TryParse(string text, string format, out IOperand value) { return this.TryParse(text, format, null, out value); }
		public bool TryParse(string text, IFormatProvider formatProvider, out IOperand value) { return this.TryParse(text, null, formatProvider, out value); }

		public int PartialParse(string text, string format, IFormatProvider formatProvider, out IOperand value)
		{
			if (this._partialParse == null)
				this._partialParse = OperandReflector.GetTryParseMethodInfo(this.OperandType);
			object[] args = new object[] { text, format, formatProvider, null };
			int RetVal = (int)this._partialParse.Invoke(null, args);
			value = args[3] as IOperand;
			return RetVal;
		}

		public int PartialParse(string text, string format, out IOperand value) { return this.PartialParse(text, format, null, out value); }
		public int PartialParse(string text, IFormatProvider formatProvider, out IOperand value) { return this.PartialParse(text, null, formatProvider, out value); }
		#endregion
	}

	public static class OperandReflector<T> where T:IOperand
	{
		static OperandParseHandler<T> parseHandler;
		static OperandTryParseHandler<T> tryParseHandler;
		static OperandPartialParseHandler<T> partialParseHandler;

		static OperandReflector()
		{
			OperandReflector<T>.parseHandler = OperandReflector.GetParseHandler<T>();
			OperandReflector<T>.tryParseHandler = OperandReflector.GetTryParseHandler<T>();
			OperandReflector<T>.partialParseHandler = OperandReflector.GetPartialParseHandler<T>();
			//OperandReflector.RegisterAllOperators(typeof(T).Assembly);
		}

		public static T Parse(string text, string format, IFormatProvider formatProvider){return OperandReflector<T>.parseHandler(text, format, formatProvider);}
		public static T Parse(string text, string format) { return OperandReflector<T>.parseHandler(text, format, null); }
		public static T Parse(string text, IFormatProvider formatProvider) { return OperandReflector<T>.parseHandler(text, null, formatProvider); }

		public static bool TryParse(string text, string format, IFormatProvider formatProvider, out T value) { return OperandReflector<T>.tryParseHandler(text, format, formatProvider, out value); }
		public static bool TryParse(string text, string format, out T value) { return OperandReflector<T>.tryParseHandler(text, format, null, out value); }
		public static bool TryParse(string text, IFormatProvider formatProvider, out T value) { return OperandReflector<T>.tryParseHandler(text, null, formatProvider, out value); }

		/// <summary>
		/// PartialParse implementations should parse as much of a <see cref="string"/> as possible
		/// and return the number of successfully parsed <see cref="char"/>s or -1 if nothing could be parsed.
		/// </summary>
		public static int PartialParse(string text, int startIndex, string format, IFormatProvider formatProvider, out T value) { return OperandReflector<T>.partialParseHandler(text, startIndex, format, formatProvider, out value); }
		public static int PartialParse(string text, int startIndex, string format, out T value) { return OperandReflector<T>.partialParseHandler(text, startIndex, format, null, out value); }
		public static int PartialParse(string text, int startIndex, IFormatProvider formatProvider, out T value) { return OperandReflector<T>.partialParseHandler(text, startIndex, null, formatProvider, out value); }

		//public static Type[] GetOperators() { return OperandReflector.GetOperators<T>(); }
	}
}
